package com.vip.utils;

import org.apache.commons.collections4.ListValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * Created by cloudy.xie on 16/12/9.
 */
public class DoubleKeyMap<K1, K2, V> implements IDoubleKeyMap<K1, K2, V> {

    private transient ListValuedMap<K1, V> key1Map = new ArrayListValuedHashMap<>();
    private transient ListValuedMap<K2, V> key2Map = new ArrayListValuedHashMap<>();

    private transient HashMap<String, DoubleValue> keyMap = new HashMap<>();

    private static final String AND = "####";

    @Override
    public int size() {
        return keyMap.size();
    }

    @Override
    public boolean isEmpty() {
        return keyMap.isEmpty();
    }

    @Override
    public boolean containKey(K1 key1, K2 key2) {
        String andKey = getAndKey(key1, key2);
        return keyMap.containsKey(andKey);
    }

    @Override
    public boolean containKey(Object key) {
        if (key1Map.containsKey(key)) {
            return true;
        }
        if (key2Map.containsKey(key)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean containKey1(K1 key1) {
        return key1Map.containsKey(key1);
    }

    @Override
    public boolean containKey2(K2 key2) {
        return key2Map.containsKey(key2);
    }

    @Override
    public V get(K1 key1, K2 key2) {
        String andKey = getAndKey(key1, key2);
        if (!keyMap.containsKey(andKey)) {
            return null;
        }

        DoubleValue doubleValue = keyMap.get(andKey);
        return key1Map.get(key1).get(doubleValue.index1);
    }

    @Override
    public List<V> getKey1(K1 key1) {
        if (!key1Map.containsKey(key1)) {
            return null;
        }
        return key1Map.get(key1);
    }

    @Override
    public List<V> getKey2(K2 key2) {
        if (!key2Map.containsKey(key2)) {
            return null;
        }
        return key2Map.get(key2);
    }

    @Override
    public V put(K1 key1, K2 key2, V value) {
        List<V> list1 = getKey1(key1);
        List<V> list2 = getKey2(key2);

        String andKey = getAndKey(key1, key2);
        DoubleValue doubleValue = new DoubleValue(key1,list2 == null ?0:list2.size() ,
                key2, list1 == null ?0:list1.size());
        keyMap.put(andKey, doubleValue);
        key1Map.put(key1,value);
        key2Map.put(key2,value);
        return value;
    }

    @Override
    public void remove(K1 key1, K2 key2) {
        String andKey = getAndKey(key1, key2);
        if (!keyMap.containsKey(andKey)) {
            return;
        }
        DoubleValue doubleValue = keyMap.get(andKey);
        key1Map.get(doubleValue.key1).remove(doubleValue.index1);
        key2Map.get(doubleValue.key2).remove(doubleValue.index2);
        keyMap.remove(andKey);
    }

    @Override
    public void removeKey1(K1 key1) {
        List<String> removeKeys = new ArrayList<>();
        keyMap.forEach((key, value) -> {
            if (Objects.equals(value.key1, key1)) {
                removeKeys.add(key);
            }
        });

        removeKeys.forEach(andKey -> {
            DoubleValue doubleValue = keyMap.get(andKey);
            key2Map.get(doubleValue.key2).remove(doubleValue.index2);
            keyMap.remove(andKey);
        });
        key1Map.remove(key1);
    }

    @Override
    public void removeKey2(K2 key2) {
        List<String> removeKeys = new ArrayList<>();
        keyMap.forEach((key, value) -> {
            if (Objects.equals(value.key2, key2)) {
                removeKeys.add(key);
            }
        });

        removeKeys.forEach(andKey -> {
            DoubleValue doubleValue = keyMap.get(andKey);
            key1Map.get(doubleValue.key1).remove(doubleValue.index1);
            keyMap.remove(andKey);
        });
        key2Map.remove(key2);
    }

    @Override
    public void clear() {
        keyMap.clear();
        key1Map.clear();
        key2Map.clear();
    }

    private String getAndKey(K1 key1, K2 key2) {
        return key1.toString() + AND + key2;
    }

    private class DoubleValue {
        private K1 key1;
        private K2 key2;
        private int index1;
        private int index2;

        public DoubleValue(K1 key1, int index1, K2 key2, int index2) {
            this.key1 = key1;
            this.key2 = key2;
            this.index1 = index1;
            this.index2 = index2;
        }

    }


    @Override
    public String toString() {
        return "keyMap="+keyMap+",key1Map="+key1Map+",key2Map="+key2Map;
    }
}
